const VSHADER_SOURCE = `
    attribute vec4 a_Position;
    attribute float a_PointSize;
    void main(){
      gl_Position = a_Position;
      gl_PointSize =a_PointSize;
    }`;

const FSHADER_SOURCE = `
    precision mediump float;
    uniform vec4 u_FragColor;
    uniform bool pointFlag;
    void main(){
        if(pointFlag){
            float dist =distance(gl_PointCoord,vec2(0.5,0.5));
            if(dist<0.5){
                gl_FragColor = u_FragColor;
            }else{
                discard;
            }
        }else{
            gl_FragColor = u_FragColor;
        }
    }`;
const webgl = document.getElementById('webgl');
const gl = getWebGLContext(webgl);
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');

if (!gl) {
    console.log('Fail');
}
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed');
}

let a_Position = gl.getAttribLocation(gl.program, 'a_Position');
let u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');
let pointFlag = gl.getUniformLocation(gl.program, 'pointFlag');
let a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize');
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Position);
// linestyle is the obj {name,solid,empty,borderWidth}
const roundJoinLine = (lineWidth = 0, lineNumber, data = 0, lineStyle) => {
    if (lineNumber <= 0 || lineWidth <= 0) return;
    let canvasWidth = canvas.offsetWidth;

    // let lineNumber = data.length;
    const translationPosition = (x1, y1, x2, y2, lineWidthHalf) => {
        let vectorXab = x2 - x1;
        let vectorYab = y2 - y1;
        let temp = lineWidthHalf * vectorXab / Math.sqrt(vectorYab * vectorYab + vectorXab * vectorXab);

        let ya1 = y1 + temp;
        let xa1 = vectorXab === 0 ? x1 - lineWidthHalf : x1 - vectorYab * (ya1 - y1) / vectorXab;
        let ya2 = y1 - temp;
        let xa2 = vectorXab === 0 ? x1 + lineWidthHalf : x1 - vectorYab * (ya2 - y1) / vectorXab;

        let xb1 = xa1 + vectorXab;
        let xb2 = xa2 + vectorXab;
        let yb1 = ya1 + vectorYab;
        let yb2 = ya2 + vectorYab;
        return [xa1, ya1, xa2, ya2, xb1, yb1, xb2, yb2];
    };

    const pointsTranslationPosition = (array, formerLineWidth, offsetIndex = 0) => {
        let lineWidth = 0.5 * formerLineWidth;
        let ArrayData = [];
        let ArrayIndex = [];
        let newArray = [];
        let index = [];
        for (let i = 0, length = array.length; i < length - 2; i += 2) {
            if (newArray.length > 5000) {
                ArrayData = ArrayData.concat(newArray);
                newArray = [];
            }
            if (index.length > 5000) {
                ArrayIndex = ArrayIndex.concat(index);
                index = [];
            }

            let [x1, y1, x2, y2] = [array[i], array[i + 1], array[i + 2], array[i + 3]];
            newArray = newArray.concat(translationPosition(x1, y1, x2, y2, lineWidth));
            let temp = (ArrayData.length + newArray.length) / 2 + offsetIndex;
            index.push(temp - 4, temp - 3, temp - 2, temp - 3, temp - 2, temp - 1);
        }

        ArrayData = ArrayData.concat(newArray);
        ArrayIndex = ArrayIndex.concat(index);

        return {
            ArrayData: ArrayData,
            ArrayIndex: ArrayIndex
        };
    }

    const drawLines = (data, lineWidth, offsetIndex = 0, lineJoin, lineColor) => {
        let newData = [];
        let index = [];
        let lineNumber = data.length;
        gl.uniform4f(u_FragColor, ...(lineColor || [0.68, 0.45, 0.55, 1.0]));
        gl.uniform1f(pointFlag, false);
        for (let i = 0; i < lineNumber; i++) {
            let data2 = data[i];
            let obj = pointsTranslationPosition(data2, lineWidth, (offsetIndex + newData.length) / 2);
            newData = newData.concat(obj.ArrayData);
            index = index.concat(obj.ArrayIndex);
            if (newData.length > 1500 || index.length > 1500) {
                gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(newData), gl.DYNAMIC_DRAW);
                gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(index), gl.DYNAMIC_DRAW);
                gl.drawElements(gl.TRIANGLES, index.length, gl.UNSIGNED_SHORT, 0);
                newData = [];
                index = [];
            }
        }
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(newData), gl.DYNAMIC_DRAW);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(index), gl.DYNAMIC_DRAW);
        gl.drawElements(gl.TRIANGLES, index.length, gl.UNSIGNED_SHORT, 0);
        newData = [];
        index = [];
        switch (lineJoin) {
            case 'round':
                {
                    ((data) => {
                        let point = [];
                        let num = 0;
                        let index = []
                        for (let i = 0, length = data.length; i < length; i++) {
                            for (let j = 0; j < data[i].length; j++) {
                                point.push(data[i][j]);
                                index.push(num++)
                            }
                        }
                        gl.vertexAttrib1f(a_PointSize, lineWidth * canvasWidth / 2);
                        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(point), gl.DYNAMIC_DRAW);
                        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(index), gl.DYNAMIC_DRAW);
                        gl.uniform1f(pointFlag, true);
                        gl.drawElements(gl.POINTS, point.length / 2, gl.UNSIGNED_SHORT, 0);
                    })(data);
                    break;
                }
        }
    }

    const dashArrayNormalData = (solid, empty, data) => {
        const canvas = document.getElementById('webgl');
        let width = canvas.offsetWidth;
        solid = solid / width;
        empty = empty / width;
        const caculate = (x0, y0, x1, y1, solid, dasharray) => {
            let makedPoint = []
            let width = Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));
            let division = Math.floor(width / dasharray);
            let multiple = width / dasharray;
            let vectorX = x1 - x0;
            let vectorY = y1 - y0;
            let vectorSolidX = (x1 - x0) * (1 / division) * (solid / dasharray);
            let vectorSolidY = (y1 - y0) * (1 / division) * (solid / dasharray);
            let multipleX = (x1 - x0) * (1 / division);
            let multipleY = (y1 - y0) * (1 / division);
            for (let i = 0; i < division - 1; i++) {
                makedPoint.push([x0 + multipleX * i, y0 + multipleY * i, (x0 + vectorSolidX) + multipleX * i, (y0 + vectorSolidY) + multipleY * i])
            }
            makedPoint.push([(x0) + multipleX * (division - 1), (y0) + multipleY * (division - 1), x1, y1]);
            return makedPoint;
        }

        let dasharray = solid + empty;
        let newPoints = [];
        let tempPoint = [];
        for (let i = 0, length = data.length; i < length; i++) {
            for (let j = 0, length1 = data[i].length; j < length1 - 2; j += 2) {
                if (tempPoint.length > 5000) {
                    newPoints = newPoints.concat(tempPoint);
                    tempPoint = [];
                }
                tempPoint = tempPoint.concat(caculate(data[i][j], data[i][j + 1], data[i][j + 2], data[i][j + 3], solid, dasharray));
            }
        }
        newPoints = newPoints.concat(tempPoint);
        tempPoint = [];
        return newPoints
    }

    let now = new Date().getTime();
    switch (lineStyle.name) {
        case 'dash-array-normal':
            {
                let newData = dashArrayNormalData(lineStyle.solidLength, lineStyle.emptyLength, data);
                drawLines(newData, lineWidth / canvasWidth, 0);
                break;
            }
        case 'dash-array-border':
            {
                drawLines(data, (lineWidth + lineStyle.borderWidth * 2) / canvasWidth, 0, lineStyle.lineJoin, [0.0, 1.0, 0.0, 1.0]);
                let newData = dashArrayNormalData(lineStyle.solidLength, lineStyle.emptyLength, data);
                drawLines(newData, lineWidth / canvasWidth, 0);
                break;
            }
        default:
            {
                drawLines(data, lineWidth / canvasWidth, 0, lineStyle.lineJoin);
                break;
            }
    }
    return new Date().getTime() - now;
};

const webglStart = (lineWidth, lineNumber, data, paramObj, num, pointNumber) => {
    document.querySelector('.webgl').addEventListener('click', e => {
        if (e.target.nodeName !== 'BUTTON') return;
        num++;
        let data1 = [];
        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT);
        if (num == 1) {
            data1 = data
        } else {
            for (let i = 0; i < lineNumber; i++) {
                let data2 = [];
                for (let j = 0; j < pointNumber * 2; j++) {
                    if (Math.random() < 0.5) {
                        data2.push((-1) * Math.random());
                    } else {
                        data2.push(Math.random());
                    }
                }
                data1.push(data2);
            }
        }
        let time = roundJoinLine(lineWidth, lineNumber, data1, paramObj);
        document.querySelector('#webglTime').innerHTML = time + ' ms';
    })

}


const canvasLine = (lineWidth, lineNumber, data, paramObj, num, pointNumber) => {
    const normalLine = (data, lineJoin, lineWidth) => {
        context.lineWidth = lineWidth / 2;
        context.strokeStyle = '#ff0000'
        if (lineJoin == null) {
            context.lineJoin = 'bevel'
        } else {
            context.lineJoin = lineJoin
        }
       
        for (let i = 0, length = data.length; i < length; i++) {
            context.beginPath();
            context.moveTo(data[i][0], data[i][1]);
            for (let j = 2; j < pointNumber * 2; j += 2) {
                context.lineTo(data[i][j], data[i][j + 1]);
               
            }
            context.stroke();
        }
    

    }

    const dasharray = (data, lineWidth, emptyLength, solidLength, lineJoin) => {
        context.setLineDash([solidLength / 2, emptyLength / 2]);
        normalLine(data, paramObj.lineJoin, lineWidth)
    }

    const dasharrayBorder = (data, lineWidth, emptyLength, solidLength, lineJoin) => {

        normalLine(data, paramObj.lineJoin, lineWidth + 4)
        context.setLineDash([solidLength / 2, emptyLength / 2]);
        normalLine(data, paramObj.lineJoin, lineWidth)
    }

    let now = new Date().getTime();
    switch (paramObj.name) {
        case 'dash-array-normal':
            {
                dasharray(data, lineWidth, paramObj.emptyLength, paramObj.solidLength, paramObj.lineJoin)
                break;
            }
        case 'dash-array-border':
            {
                dasharrayBorder(data, lineWidth, paramObj.emptyLength, paramObj.solidLength, paramObj.lineJoin)
                break;
            }
        default:
            {
                normalLine(data, paramObj.lineJoin, lineWidth)
                break;
            }
    }
    return new Date().getTime() - now;

}
const canvasStart = (lineWidth, lineNumber, canvasData, paramObj, num, pointNumber) => {
    document.querySelector('.canvas').addEventListener('click', e => {
        if (e.target.nodeName !== 'BUTTON') return;
        num++;
        let data1 = [];
        let now = new Date().getTime();

        canvas.width = canvas.width;
        canvas.height = canvas.width;
        if (num == 1) {
            data1 = canvasData
        } else {
            for (let i = 0; i < lineNumber; i++) {
                let data2 = [];
                for (let j = 0; j < pointNumber * 2; j++) {
                    data2.push(Math.random() * canvas.width)
                }
                data1.push(data2);
            }
        }
        console.log(new Date().getTime() - now);
        let time = canvasLine(lineWidth, lineNumber, data1, paramObj, num, pointNumber);

        document.querySelector('#canvasTime').innerHTML = time + ' ms';
    })
}


document.querySelector('#line-control').addEventListener('click', e => {
    if (e.target.nodeName !== 'BUTTON') return;
    canvas.width = canvas.width;
    canvas.height = canvas.width;
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    let paramObj = {};
    let lineType = document.querySelector('#line-control .line-type-select').value;
    paramObj['name'] = lineType;
    if (!document.getElementById('dash-array-line-dash').classList.contains('dash-array-line-dash-display')) {
        let solidLength = document.querySelector('#dash-array-line-dash input[name="solid-length"]').value;
        let emptyLength = document.querySelector('#dash-array-line-dash input[name="empty-length"]').value;
        paramObj['solidLength'] = Number(solidLength);
        paramObj['emptyLength'] = Number(emptyLength);
    }
    paramObj['borderWidth'] = 10.0;
    paramObj['lineJoin'] = document.querySelector('#line-control .line-join').value;
    let lineWidth = Number(e.target.parentNode.querySelector('input[name="line-width"]').value);
    let lineNumber = Number(e.target.parentNode.querySelector('input[name="line-number"]').value);
    let pointNumber = 3;
    let data = [];
    let num = 0;
    for (let i = 0; i < lineNumber; i++) {
        let data2 = [];
        for (let j = 0; j < pointNumber * 2; j++) {
            if (Math.random() < 0.5) {
                data2.push((-1) * Math.random());
            } else {
                data2.push(Math.random());
            }
        }
        data.push(data2);
    }
    let canvasData = []
    for (let i = 0; i < lineNumber; i++) {
        let data2 = [];
        for (let j = 0; j < pointNumber * 2; j++) {
            data2.push(Math.random() * canvas.width)
        }
        canvasData.push(data2);
    }
    webglStart(lineWidth, lineNumber, data, paramObj, num, pointNumber);
    canvasStart(lineWidth, lineNumber, canvasData, paramObj, num, pointNumber)



    // {
    //     name: 'dash-array-border',
    //         solidLength: 100,
    //             emptyLength: 100,
    //                 borderWidth: 10.0,
    //                     lineJoin: 'round'
    // }

})

document.querySelector('#line-control .line-type-select').addEventListener('change', e => {
    let lineType = e.target.value;
    if (lineType && lineType.match(/dash\-array/mg)) {
        document.getElementById('dash-array-line-dash').classList.remove('dash-array-line-dash-display');
        return;
    }
    document.getElementById('dash-array-line-dash').classList.add('dash-array-line-dash-display');
})